package a5.grammar.productions;

import java.util.LinkedList;
import java.util.List;

import a5.Interpreter;
import a5.common.ProgramCursor;
import a5.common.OrderedTree;
import a5.common.Token;
import a5.environment.Environment;
import a5.grammar.annotations.Grammar;
import a5.grammar.grammars.G1;
import a5.grammar.interfaces.Production;

/**
 * <pre>
 * This class provides a method for interpreting the following production
 * Binding -> VariableName Expr | UserFunctionName Definition
 * 
 * In plain text, this production is composed of a name (either "VariableName" or "UserFunctionName")
 * and a value which should be bound to the name in the {@link a5.Interpreter Interpreter's} 
 * {@link a5.environment.Environment Environment}. The value assigned to the name may be either the
 * result of {@link a5.grammar.productions.Expr#interpret(Interpreter) Expr.interpret()} (for "VariableName") 
 * or a new {@link a5.function.Function Function} {@link a5.grammar.g1.production.production.Definition Definition}. Quite simply,
 * this production is an assignment operator.
 * 
 * This production uses recursive decent to interpret and parse.
 * </pre>
 * 
 * @author Jason Campos
 * @version Assignment 5, CS 152 (Programming Paradigms) Fall 2012, Jeffrey Smith
 */
@Grammar(clazz=G1.class)
public class Binding implements Production {
	// Production dependencies
	private Expr expr;
	private Definition definition;
	
	// hide constructor
	private Binding(){}
	
	/**
	 * Recursively interprets the program of the argument cursor using the variable/function scoping
	 * policies of the argument environment.
	 * 
	 * The end result of this production is the storage of a new variable
	 * or {@link a5.function Function} within the argument {@link a5.environment.Environment Environment}. 
	 * 
	 * @param environment
	 * The {@link a5.environment.Environment Environment} responsible for variable and function maintenance
	 * and scoping. 
	 * 
	 * @param cursor
	 * The {@link a5.common.ProgramCursor ProgramCursor} which contains the {@link a5.common.Token Tokens} 
	 * of the program being interpreted.
	 * 
	 */
	public void interpret(Environment environment, ProgramCursor cursor) {
		// Use interpreter.match() to extract the text at the current point of execution.
		// If this point contains a "VariableName", use Expr.interpret() to determine the 
		// value of the variable. Otherwise, assume that the point of execution rests on a
		// "UserFunctionName". Bind the text of the resolved function name to a new 
		// {@link a5.production.Definition Definition}.
		if(cursor.first("VariableName")){
			environment.setVariable(cursor.match(environment, "VariableName"), expr.interpret(environment, cursor));
		} else {
			String functionName = cursor.match(environment, "UserFunctionName");
			environment.setFunction(functionName, definition.interpret(functionName, environment, cursor));
		}
	}
	
	/**
	 * Recursively parses the program of the argument cursor using the variable/function scoping
	 * policies of the argument environment.
	 * 
	 * @param cursor
	 * The {@link a5.common.ProgramCursor ProgramCursor} which contains the {@link a5.common.Token Tokens} 
	 * of the program being interpreted.
	 * 
	 * @return
	 * An {@link a5.common.OrderedTree OrderedTree<Token>} representing the parse structure of the 
	 * the program of the argument cursor.
	 */
	public OrderedTree<Token> parse(ProgramCursor cursor){
		List<OrderedTree<Token>> children = new LinkedList<OrderedTree<Token>>();
		
		// Peek at token type to determine which rule to apply
		if(cursor.first("VariableName")){
			// Binding -> VariableName Expr
			children.add(new OrderedTree<Token>(new Token(cursor.match("VariableName"))));
			children.add(expr.parse(cursor));
		} else {
			 // Binding -> UserFunctionName Definition
			children.add(new OrderedTree<Token>(new Token(cursor.match("UserFunctionName"))));
			children.add(definition.parse(cursor));
		}
		
		return new OrderedTree<Token>(new Token(this.getClass().getSimpleName()),children);
	}
	
	/** 
	 * @param cursor
	 * The {@link a5.common.ProgramCursor ProgramCursor} which contains the {@link a5.common.Token Tokens} 
	 * of the program being interpreted.
	 * 
	 * @return
	 * {@code true} if the execution point of the program is at a point which indicates that
	 * an Binding is a valid path of execution. In other words, if the current point of
	 * execution contains an element which is the first element of this production 
	 * ("VariableName", "UserFunctionName").
	 */
	public boolean first(ProgramCursor cursor) {
		return cursor.first("VariableName") || cursor.first("UserFunctionName");
	}
}
